#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>

#include "action.hpp"
#include "factory.hpp"

class Shell : public Action
{
  private:
    std::string command;
    pid_t pid = -1;

  public:
    Shell(const nlohmann::json &j) : Action(j) { j.at("command").get_to(command); }
    virtual bool Execute() override final
    {
        pid = fork();
        if (pid == 0)
        { // child process
            execl("/bin/bash", "bash", "-c", command.c_str(), (char *)0);
            // if execl fails, the program will terminate
            exit(1);
        }
        else if (pid < 0)
        { // fork failed
            spdlog::error("Creat subprocess failed");
            return false;
        }
        else
        { // parent process
            spdlog::info("child process executing on pid: {}", getpid());
            int status;
            waitpid(pid, &status, 0);
            if (WIFEXITED(status))
            {
                spdlog::info("Shell action completed with status {}", WEXITSTATUS(status));
                return true;
            }
            else
            {
                if (WIFSIGNALED(status))
                {
                    spdlog::warn("Shell action terminated with signal {}({})", strsignal(WTERMSIG(status)),
                                 WTERMSIG(status));
                    return true;
                }
                spdlog::error("Shell action terminated with status {}", WEXITSTATUS(status));
                return false;
            }
        }
    }
    virtual void Terminate() final
    {
        if (pid > 0)
        {
            kill(pid, SIGTERM);
            std::this_thread::sleep_for(duration);
            int status;
            waitpid(pid, &status, WNOHANG);
            if (status == 0)
            {
                kill(pid, SIGKILL);
                spdlog::info("Time out, force terminating shell action <{}>:{}", type, name);
            }
        }
    }
};

static Factory<Action>::Register<Shell> reg("shell");